﻿using System;
using System.Windows;
using System.Collections.Generic;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using Bling.DSL;
using Bling.WPF;
using Bling.Shapes;
using Bling.Animation;
using Bling.Util;
using Bling.Physics;
using Bling.D3D;
using Bling.Lighting;


namespace Bling.PhysicsTutorial {
 public enum CloseDirection {FROMLEFT, FROMRIGHT};

 
  public class Curtain3D : PhysicsCanvas {
    public Curtain3D() : base("Curtain", 30)  {}
    protected override Action Init(CanvasBl MainCanvas, World World) {
      Background = Brushes.LightBlue;
      int ColumnsN = 15;
      int RowsN = 11;
      var Flowers = Properties.Resources.flowers;

      DoubleBl ratio = (MainCanvas.Size.X*0.5 / MainCanvas.Size.Y);
      var ColumnWidth = MainCanvas.Size.X*0.5 / (ColumnsN + 1);
      var RowHeight = MainCanvas.Size.Y / (RowsN + 1);
      var dim = new PointBl(ColumnWidth, RowHeight);

      DoubleBl x_length = ratio;
      DoubleBl y_length = 1.0;

      BoolBl ThumbVisible = true;
      Position3DBlock<Thumb, ThumbBl> blockA = new Position3DBlock<Thumb, ThumbBl>(World, ColumnsN * RowsN, i => {
        var ret = new ThumbBl(MainCanvas) {
          CanDrag = true, Background = i.SelectColor(), ZIndex = 3,
          Visibility = ThumbVisible,
        };
        if (i == 0) ThumbVisible = ret.Visibility;
        ret.ToolTip = "I:".Bl() + i + " C:" + (i % ColumnsN) + " R:" + (i / RowsN) + " PT:" + 
          ((ret.CenterPosition - new PointBl(dim.X, 0d)) / (MainCanvas.Size - new PointBl(2d * dim.X, 1d * dim.Y)) * new PointBl(x_length, y_length)).RoundN(100);
        return ret;
      });
      var PreserveProperty = blockA.NewBodyProperty<bool>("Preserve");
      Func<Position3DBlock<Thumb, ThumbBl>.Body, BoolBl> Preserve = body => body.GetProperty(PreserveProperty);

      CheckBoxBl Do3D = new CheckBoxBl(MainCanvas) {
        Content = "3D", ZIndex = 100,
        IsChecked = true, RightBottom = MainCanvas.Size,
      };

      for (int i = 0; i < ColumnsN - 1; i++) {
        for (int j = 0; j < RowsN - 1; j++) {
          ThumbBl p0 = blockA.Get((j + 0) * ColumnsN + i + 0).Bl();
          ThumbBl p1 = blockA.Get((j + 0) * ColumnsN + i + 1).Bl();
          ThumbBl p2 = blockA.Get((j + 1) * ColumnsN + i + 1).Bl();
          ThumbBl p3 = blockA.Get((j + 1) * ColumnsN + i + 0).Bl();

          PointBl LeftTop = p0;
          LeftTop = LeftTop.Min(p1).Min(p2).Min(p3);
          PointBl RightBot = p0;
          RightBot = RightBot.Max(p1).Max(p2).Max(p3);


          var cell = (j * ColumnsN) + i;
          if (true) {
            var shape = new PathShapeBl(MainCanvas) {
              Fill = cell.SelectColor(), ZIndex = 2, Opacity = 1,
              Visibility = !Do3D.IsChecked,
              Segments = {
                AddMany = new PathSegmentBl[] { p1.CenterPosition, p2.CenterPosition, p3.CenterPosition, p0.CenterPosition },
              },
              Start = p0,
            };
            shape.Start = p0;
            /*
            shape.Add(new LineSegmentBl() { Point = p1 });
            shape.Add(new LineSegmentBl() { Point = p2 });
            shape.Add(new LineSegmentBl() { Point = p3 });
            shape.Add(new LineSegmentBl() { Point = p0 });
             */
          }
        }
      }
      var meshLeft = new MeshGeometry3DBl();
      var meshRight = new MeshGeometry3DBl();
      // no additional columns
      int x_node_count = ColumnsN + 0;
      // one additional row.
      int y_node_count = RowsN + 1;
      DoubleBl x_step = x_length / ((double)(x_node_count - 1));
      DoubleBl y_step = y_length / ((double)(y_node_count - 1));
      {
        List<Point3DBl> Positions = new List<Point3DBl>();
        List<IntBl> TriangleIndices = new List<IntBl>();
        List<PointBl> TextureCoord = new List<PointBl>();

        DoubleBl x_pos = 0.0;

        for (int x = 0; x < x_node_count; x++) {
          DoubleBl y_pos = 0.0;

          for (int y = 0; y < y_node_count; y++) {
            Positions.Add(new Point3DBl(x_pos, y_pos, 0));
            y_pos += y_step;

            double xx = x;
            double xn = x_node_count - 1d;
            double yy = y;
            double yn = y_node_count - 1d;
            TextureCoord.Add(new PointBl(xx / xn, 1.0 - yy / yn));
          }
          x_pos += x_step;
        }
        for (int x = 0; x < x_node_count - 1; x++) {
          for (int y = 0; y < y_node_count - 1; y++) {
            if ((x + y) % 2 == 1) {
              TriangleIndices.Add(y + (x * y_node_count));
              TriangleIndices.Add((y) + ((x + 1) * y_node_count));
              TriangleIndices.Add((y + 1) + x * y_node_count);

              TriangleIndices.Add(y + ((x + 1) * y_node_count));
              TriangleIndices.Add((y + 1) + ((x + 1) * y_node_count));
              TriangleIndices.Add((y + 1) + ((x) * y_node_count));
            } else {
              TriangleIndices.Add(y + (x * y_node_count));
              TriangleIndices.Add((y + 1) + ((x + 1) * y_node_count));
              TriangleIndices.Add((y + 1) + x * y_node_count);

              TriangleIndices.Add(y + (x * y_node_count));
              TriangleIndices.Add(y + ((x + 1) * y_node_count));
              TriangleIndices.Add((y + 1) + ((x + 1) * y_node_count));
            }
          }
        }
        meshLeft.Positions.InitElements(Positions);
        meshLeft.TextureCoordinates.InitElements(TextureCoord);
        meshLeft.TriangleIndices.InitElements(TriangleIndices);
        meshRight.Positions.InitElements(Positions);
        meshRight.TextureCoordinates.InitElements(TextureCoord);
        meshRight.TriangleIndices.InitElements(TriangleIndices);
      }

      Viewport3DBl vpLeft = new Viewport3DBl(MainCanvas) {
        LeftTop = new PointBl(0, 0),
        Size = new PointBl(MainCanvas.Width*0.5, MainCanvas.Height),
        ClipToBounds = false,
        Visibility = Do3D.IsChecked,
        IsHitTestVisible = false,
        EdgeMode = EdgeMode.Aliased,
        BitmapScalingMode = BitmapScalingMode.NearestNeighbor,
        Children = {
          AddOne = new ModelVisual3DBl() {
            Content = new SpotLightBl() {
              Color = Colors.White,
              Position = new Point3DBl(x_length, y_length * 0.25, 3),
              Direction = new Point3DBl(0, 0, -1),//(/*1.2*/ sliderA.Value, /*0.1*/ sliderB.Value, /*-1.1*/ sliderC.Value),
              InnerConeAngle = 70d.ToDegrees(),
              OuterConeAngle = 100d.ToDegrees(),
            },
          },
        },
        Camera = new OrthographicCameraBl() {
          Position = new Point3DBl(x_length * 0.5, y_length * 0.5, 1.0),
          Direction = {
            Look = new Point3DBl(0, 0, -1),
            Up = new Point3DBl(0, 1, 0),
          },
          Width = x_length, PlaneDistance = { Far = 5, Near = 0.1 },
        },
      };
       Viewport3DBl vpRight = new Viewport3DBl(MainCanvas) {
        LeftTop = new PointBl(MainCanvas.Width*0.5, 0),
        Size = new PointBl(MainCanvas.Width*0.5, MainCanvas.Height),
        ClipToBounds = false,
        Visibility = Do3D.IsChecked,
        IsHitTestVisible = false,
        EdgeMode = EdgeMode.Aliased,
        BitmapScalingMode = BitmapScalingMode.NearestNeighbor,
        Children = {
          AddOne = new ModelVisual3DBl() {
            Content = new SpotLightBl() {
              Color = Colors.White,
              Position = new Point3DBl(0, y_length * 0.25, 3),
              Direction = new Point3DBl(0, 0, -1),//(/*1.2*/ sliderA.Value, /*0.1*/ sliderB.Value, /*-1.1*/ sliderC.Value),
              InnerConeAngle = 70d.ToDegrees(),
              OuterConeAngle = 100d.ToDegrees(),
            },
          },
        },
        Camera = new OrthographicCameraBl() {
          Position = new Point3DBl(x_length * 0.5, y_length * 0.5, 1.0),
          Direction = {
            Look = new Point3DBl(0, 0, -1),
            Up = new Point3DBl(0, 1, 0),
          },
          Width = x_length, PlaneDistance = { Far = 5, Near = 0.1 },
        },
      };
      var box = new TextBoxBl(MainCanvas) {
        LeftBottom = new PointBl(0, MainCanvas.Height - 50d),
        Width = MainCanvas.Width*0.5, ZIndex = 100,
        Font = { Size = 40, Weight = FontWeights.SemiBold, /*Family = "Arial".Convert<FontFamily>(),*/ },
        Background = Brushes.Transparent, Foreground = (!Do3D.IsChecked).Condition(Brushes.Black, Brushes.Transparent),
      };
      var HitStrength = new LabelSliderBl(MainCanvas) {
        ZIndex = box.ZIndex, LeftBottom = box.LeftTop,
        Minimum = 10, Maximum = 100, Value = 50, LabelName = "Hit",
      };
      var Friction = new LabelSliderBl(MainCanvas) {
        ZIndex = box.ZIndex, LeftBottom = HitStrength.LeftTop,
        Minimum = 0, Maximum = .1, Value = .025, LabelName = "Fric",
      };
      var ZUp = new LabelSliderBl(MainCanvas) {
        ZIndex = box.ZIndex, LeftBottom = Friction.LeftTop,
        Minimum = 0, Maximum = .25, Value = .15, LabelName = "ZUp",
      };
      CheckBoxBl Tied = new CheckBoxBl(MainCanvas) {
        Content = "Tiled", ZIndex = 100, Font = {Size = 24}, 
        IsChecked = false, LeftBottom = ZUp.LeftTop,
      };
      var CloseProgress = new LabelSliderBl(MainCanvas) {
        ZIndex = box.ZIndex, LeftTop = Tied.RightTop, Width = MainCanvas.Width - Tied.Width,
        Minimum = 0, Maximum = 0.99, Value = 0, LabelName = "Close",
      };
      var clothLeft = new CanvasBl() {
        /*Background = Brushes.LightSeaGreen,*/        
        Background = new ImageBrushBl() {
          ImageSource = Flowers, 
          TileMode = TileMode.Tile,
          Stretch = Stretch.Fill,
        },

        Size = new PointBl(MainCanvas.Width * 0.5, MainCanvas.Height),
      };
      var clothRight = new CanvasBl() {
        /*Background = Brushes.LightSeaGreen,*/        
        Background = new ImageBrushBl() {
          ImageSource = Flowers, 
          TileMode = TileMode.Tile,
          Stretch = Stretch.Fill,
        },

        Size = new PointBl(MainCanvas.Width * 0.5, MainCanvas.Height),
      };

      var box2 = new TextBlockBl(clothLeft) {
        Text = box.Text, Font = box.Font,
        LeftTop = box.LeftTop, Size = box.Size,
        Foreground = Brushes.Black,
      };

      vpLeft.Children.AddOne = new Viewport2DVisual3DBl() {
        Material = new DiffuseMaterialBl() {
          IsVisualHostMaterial = true, // XXX: should freeze.
        },
        CachingHint = CachingHint.Cache,
        CacheInvalidationThreshold = {
          Minimum = 0.001,
          Maximum = 1000,
        },
        Visual = clothLeft,//textBox,
        Geometry = meshLeft,
      };
      vpRight.Children.AddOne = new Viewport2DVisual3DBl() {
        Material = new DiffuseMaterialBl() {
          IsVisualHostMaterial = true, // XXX: should freeze.
        },
        CachingHint = CachingHint.Cache,
        CacheInvalidationThreshold = {
          Minimum = 0.001,
          Maximum = 1000,
        },
        Visual = clothRight,//textBox,
        Geometry = meshRight,
      };
      // physical constraints
      {
        // Again, a physical position for each thumb (we did this in Chain also).


        SliderBl PointLess = new SliderBl();

        blockA.ForAll((body) => {
          Preserve(body).Init = false; 
          body.Position3D().X.Link.To = body.At.CenterPosition.X;
          body.Position3D().Y.Link.To = body.At.CenterPosition.Y;
          //body.Position3D().Link[!body.At.IsDragging] = new Point3DBl(body.At.CenterPosition.X, body.At.CenterPosition.Y, PointLess);


          var dim0 = new PointBl(dim.X * (1d - CloseProgress.Value), dim.Y);
          GridManager2 Grid = new GridManager2() {
            ColumnCount = ColumnsN, RowCount = RowsN,
            CellSize = dim0, LeftTop = new PointBl(0, 0), Size = MainCanvas.Size * new PointBl(1 * (1d - CloseProgress.Value), 1),
          };


          //body.Position3D().Link/*[!body.At.IsDragging]*/.To = ((Point3DBl) body.At.CenterPosition);
          IntBl Column = body.Index % Grid.ColumnCount;
          IntBl Row = body.Index / Grid.ColumnCount;
          body.Position3D().Init = new PointBl(10d, 10d) * new PointBl(Column * 30d.Bl(), Row * 20d.Bl());


          body.Position3D().Step = (now, old) => now.VerletStep(Friction, old);
          //body.Position().Bind += new PointBl(0, .05d);

          Point3DBl p = new PointBl(0, 0);
          var useBody = body.CursorPosition3D();

          p += body.Position3D().Spring(Grid.Cell(useBody, +1, +0), dim0.X);
          p += body.Position3D().Spring(Grid.Cell(useBody, -1, +0), dim0.X);
          p += body.Position3D().Spring(Grid.Cell(useBody, +0, +1), dim0.Y);
          p += body.Position3D().Spring(Grid.Cell(useBody, +0, -1), dim0.Y);
          p += body.Position3D().Spring(Grid.Cell(useBody, +1, +1), dim0.Length);
          p += body.Position3D().Spring(Grid.Cell(useBody, -1, -1), dim0.Length);
          //p += body.Position3D().Spring(Grid.Cell(useBody, -2, +2), dim0.Length * 2);
          //p += body.Position3D().Spring(Grid.Cell(useBody, +2, -2), dim0.Length * 2);
          
          
          //p += body.Position().Spring(Grid.Cell(useBody, -2, +0), dim.X*2);
          //p += body.Position().Spring(Grid.Cell(useBody, +2, +0), dim.X*2);
          //p += body.Position().Spring(Grid.Cell(useBody, +0, -2), dim.Y*2);
          //p += body.Position().Spring(Grid.Cell(useBody, +0, +2), dim.Y*2);

          p = p / 6d;
          p = Preserve(body).Condition(body.Position3D().Old, p);
          //p = (cb.IsChecked & Column == 0 & (Row == 3 | Row == 4)).Condition(body.Position3D().Old, p);

          body.Position3D().Relax[1, true] = p;

          var minDim = new Point3DBl(dim, -100);
          var maxDim0 = MainCanvas.Size - dim;
          var maxDim = new Point3DBl(maxDim0, +100);

          body.Position3D().Relax[1] = body.Position3D().Clamp(minDim, maxDim);
        });
      }

      return () => {
        Random rr = new Random();
        Action<int> doSomething = Point3DBl.Assign<int>(i => blockA[i].Position3D(), i => blockA[i].Position3D() +
          //new Point3DBl(1, 1, 1) * (HitStrength.Value * -1d) +
          //((Point3DBl)rr) * new Point3DBl(1, 1, 1) * (HitStrength.Value * 2d)
          new Point3DBl(.5, .5, -1) * HitStrength
          );
        Action<int> doSomethingElse = Point3DBl.Assign<int>(i => blockA[i].Position3D(), i => blockA[i].Position3D() +
          new Point3DBl(-240, 0, 1.5)
          );
        Action<int> togglePreserve = BoolBl.Assign<int>(i => Preserve(blockA[i]), i => !Preserve(blockA[i]));
        var button = new ButtonBl(MainCanvas) {
          LeftBottom = new PointBl(0, MainCanvas.Height),
          Content = "Push", Click = () => {
            doSomething(rr.Next(0, blockA.Count));
          },
          ZIndex = 100,
        };
        var button2 = new ButtonBl(MainCanvas) {
          LeftBottom = button.RightBottom,
          Content = "Push a hole", Click = () => {
            for(int i = 4; i < RowsN - 3; i++){
              doSomethingElse(i * ColumnsN - 1);
              if (i == 5 || i == 6) {
                doSomethingElse(i * ColumnsN - 1);
              }
              togglePreserve(i * ColumnsN - 1);
            }
          },
          ZIndex = 100,
        };
        ThumbVisible.Bind = true; // !button.IsMouseOver;
        var cbox = box.CurrentValue;
        cbox.TextChanged += (x, y) => {
          if (y.Changes.Count == 1) {
            foreach (var change in y.Changes) {
              if ((change.AddedLength == 1 && change.RemovedLength == 0)
              || (change.AddedLength == 0 && change.RemovedLength == 1)) {
                var xPos = cbox.GetRectFromCharacterIndex(cbox.Text.Length).Left;


                var pos = new PointBl(xPos, MainCanvas.Height);
                pos = (pos - new PointBl(dim.X, 0d)) / (MainCanvas.Size - new PointBl(2d * dim.X, 1d * dim.Y));
                pos = pos * new PointBl(x_length, y_length);
                //int row = (int)(pos.Y / y_step).CurrentValue;
                //row = (row >= RowsN) ? RowsN - 1 : row;
                for (int row = 0; row < RowsN; row++) {
                  int col = (int)(pos.X * 2 / x_step).CurrentValue;
                  col = Math.Min(col, ColumnsN - 1);
                  //col = (row >= ColumnsN) ? ColumnsN - 1 : col;
                  doSomething(row * ColumnsN + col);
                }
              }
            }
          }
        };

        MainCanvas.OnMouseDown = (x, y) => {
          var pos = MainCanvas.MousePosition;
          pos = (pos - new PointBl(dim.X, 0d)) / (MainCanvas.Size - new PointBl(2d * dim.X, 1d * dim.Y));
          pos = pos * new PointBl(x_length, y_length);
          int row = (int)(pos.Y / y_step).CurrentValue;
          row = (row >= RowsN) ? RowsN - 1 : row;
          int col = (int)(pos.X / x_step).CurrentValue;
          col = (row >= ColumnsN) ? ColumnsN - 1 : col;
          doSomething(row*ColumnsN + col);
          return true;
        };

        var relSize = (MainCanvas.Size - new PointBl(2d * dim.X, 1d * dim.Y));
        Func<PointBl, PointBl> doSomeTransform = (p) => {
          var temp = (p - new PointBl(dim.X, 0d)) / relSize;
          temp = temp * new PointBl(x_length, y_length);
          return temp;
        };

        List<Point3DBl> RefreshLeft = new List<Point3DBl>();
        List<Point3DBl> RefreshRight = new List<Point3DBl>();
        DoubleBl x_pos = 0.0;
        for (int x = 0; x < x_node_count; x++) {
          DoubleBl y_pos = 0.0;
          for (int y = 0; y < y_node_count; y++) {
            if (y == y_node_count - 1) {
              //Refresh.Add(new Point3DBl(x_pos, y_pos, 0d));
              var closeProgress = (Tied.IsChecked).Condition((CloseProgress.Value * 0.05).Max(0d).Min(1d), CloseProgress.Value);
              if (x == 0) {
                RefreshLeft.Add(new Point3DBl(0, y_pos, 0));

                var thumb = (PointBl)(blockA[x_node_count - 1].Position3D());
                thumb = new PointBl(MainCanvas.Width - thumb.X, thumb.Y);
                thumb = doSomeTransform(thumb);
                RefreshRight.Add(new Point3DBl(0.5d.Bl().Lerp(x_length * closeProgress, thumb.X), y_pos, ((x_node_count - 1) % 4 > 1 ? closeProgress.Lerp(0, ZUp.Value) : 0)));

              } else if (x == x_node_count - 1) {
                var thumb = (PointBl)(blockA[x_node_count - 1].Position3D());
                thumb = doSomeTransform(thumb);
                RefreshLeft.Add(new Point3DBl(0.5d.Bl().Lerp(x_length * (1d - closeProgress), thumb.X), y_pos, (x % 4 > 1 ? closeProgress.Lerp(0, ZUp.Value) : 0)));
                
                RefreshRight.Add(new Point3DBl(x_length, y_pos, 0));
              } else {

                var idx = x;
                PointBl thumb = (PointBl)(blockA[idx].Position3D());
                thumb = doSomeTransform(thumb);
                RefreshLeft.Add(new Point3DBl(0.5d.Bl().Lerp(x_pos * (1d - closeProgress), thumb.X), y_pos, (x % 4 > 1 ? closeProgress.Lerp(0, ZUp.Value) : 0)));

                idx = x_node_count - 1 - x;
                thumb = (PointBl)(blockA[idx].Position3D());
                thumb = new PointBl(MainCanvas.Width - thumb.X, thumb.Y);
                thumb = doSomeTransform(thumb);
                RefreshRight.Add(new Point3DBl(0.5d.Bl().Lerp(x_pos + (x_length - x_pos) * closeProgress, thumb.X), y_pos, (idx % 4 > 1 ? closeProgress.Lerp(0, ZUp.Value) : 0)));
              }
            } else {

              var y0 = (y_node_count - y - 2);
              // if y is 0, now it is y_node_count - 1
              // if y is y_node_count - 1, now it is 0
              var closeProgress = (Tied.IsChecked & y0 < 4).Condition((CloseProgress.Value * (y0 * y0 * 0.1 + 0.1)).Max(0d).Min(1d), CloseProgress.Value);

              var idx = y0 * ColumnsN + x;
              PointBl thumb = (PointBl) (blockA[idx].Position3D());
              thumb = doSomeTransform(thumb);
              thumb = new PointBl(thumb.X, y_length - thumb.Y);

              var xy_pos = new PointBl(x_pos * (1d - closeProgress), y_pos + ((y == 0 && x % 4 > 1) ? 0.03 : 0));
              xy_pos = 0.5d.Bl().Lerp(xy_pos, thumb);
 
              RefreshLeft.Add(new Point3DBl(xy_pos, blockA[idx].Position3D().Z / 500d +
                (x % 4 > 1 ? closeProgress.Lerp(0, ZUp.Value) : 0)));

              idx = y0 * ColumnsN + x_node_count - 1 - x;
              thumb = (PointBl) (blockA[idx].Position3D());
              thumb = new PointBl(MainCanvas.Width - thumb.X, thumb.Y);
              thumb = doSomeTransform(thumb);
              thumb = new PointBl(thumb.X, y_length - thumb.Y);

              xy_pos = new PointBl(x_pos + (x_length - x_pos) * closeProgress, y_pos + ((y == 0 && (x_node_count - 1 - x) % 4 > 1) ? 0.03 : 0));
              xy_pos = 0.5d.Bl().Lerp(xy_pos, thumb);

              RefreshRight.Add(new Point3DBl(xy_pos, blockA[idx].Position3D().Z / 500d +
                ((x_node_count - 1 - x) % 4 > 1 ? closeProgress.Lerp(0, ZUp.Value) : 0)));
            }
            y_pos += y_step;

          }
          x_pos += x_step;
        }
        Action a = meshLeft.Positions.RefreshAction(RefreshLeft);
        Action b = meshRight.Positions.RefreshAction(RefreshRight);
        Func<bool> doRefresh = Do3D.IsChecked.AsFunc;
        Action aa = () => {
          if (doRefresh()) {
            a();
            b();
          }
        };
        World.UpdateInUI.Add(aa);

      };

    }
  }


}
